/** * © Copyright IBM Corporation 2015 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ package com.ibm.watson.developer_cloud.android.speech_to_text.v1.audio; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.channels.NotYetConnectedException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Map; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import org.java_websocket.client.WebSocketClient; import org.java_websocket.drafts.Draft_17; import org.java_websocket.handshake.ServerHandshake; import org.json.JSONException; import org.json.JSONObject; import android.util.Log; import com.ibm.watson.developer_cloud.android.speech_to_text.v1.dto.SpeechConfiguration; import com.ibm.watson.developer_cloud.android.speech_to_text.v1.ISpeechDelegate; public class WebSocketUploader extends WebSocketClient implements IChunkUploader { // Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice. public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2015"; private static final String TAG = WebSocketUploader.class.getName(); private ISpeechEncoder encoder = null; private Thread initStreamToServerThread; private boolean uploadPrepared = false; /** STT delegate */ private ISpeechDelegate delegate = null; /** Recorder delegate */ private SpeechConfiguration sConfig = null; /** * Create an uploader which supports streaming. * * @param serverURL LMC server, delivery to back end server * @throws URISyntaxException */ public WebSocketUploader(String serverURL, Map<String, String> header, SpeechConfiguration config) throws URISyntaxException { super(new URI(serverURL), new Draft_17(), header, config.connectionTimeout); Log.d(TAG, "New WebSocketUploader: " + serverURL); Log.d(TAG, serverURL); this.sConfig = config; if(sConfig.audioFormat.equals(SpeechConfiguration.AUDIO_FORMAT_DEFAULT)) { this.encoder = new RawEnc(); } else if(sConfig.audioFormat.equals(SpeechConfiguration.AUDIO_FORMAT_OGGOPUS)){ this.encoder = new OggOpusEnc(); } if(serverURL.toLowerCase().startsWith("wss") || serverURL.toLowerCase().startsWith("https")) this.sConfig.isSSL = true; else this.sConfig.isSSL = false; } /** * Trust server * * @throws KeyManagementException * @throws NoSuchAlgorithmException */ private void trustServer() throws KeyManagementException, NoSuchAlgorithmException, IOException { // Create a trust manager that does not validate certificate chains TrustManager[] certs = new TrustManager[]{ new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[]{}; } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {} public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {} }}; SSLContext sslContext = null; sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, certs, new java.security.SecureRandom()); SSLSocketFactory factory = sslContext.getSocketFactory(); this.setSocket(factory.createSocket()); } /** * 1. Initialize WebSocket connection </br> * 2. Init an encoder and writer * * @throws Exception */ private void initStreamAudioToServer() throws Exception { Log.d(TAG, "Connecting..."); //lifted up for initializing writer, using isRunning to control the flow this.encoder.initEncoderWithUploader(this); if(this.sConfig.isSSL) this.trustServer(); boolean rc; rc = this.connectBlocking(); if (rc) { Log.d(TAG, "Connected"); this.sendSpeechHeader(); } else{ Log.e(TAG, "Connection failed!"); this.uploadPrepared = false; throw new Exception("Connection failed!"); } } @Override public int onHasData(byte[] buffer) { int uploadedAudioSize = 0; // NOW, WE HAVE STATUS OF UPLOAD PREPARING, UPLOAD PREPARING OK if (this.isUploadPrepared()) { try { uploadedAudioSize = encoder.encodeAndWrite(buffer); Log.d(TAG, "onHasData: " + uploadedAudioSize + " " + buffer.length); // TODO: Capturing data } catch (IOException e) { e.printStackTrace(); } } else { try { Log.w(TAG, "waiting for establishing the connection"); initStreamToServerThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } return uploadedAudioSize; } @Override public boolean isUploadPrepared() { return this.uploadPrepared; } public void stopUploaderPrepareThread() { if (initStreamToServerThread != null) { initStreamToServerThread.interrupt(); } } /** * Prepare connection */ @Override public void prepare() { this.uploadPrepared = false; initStreamToServerThread = new Thread() { public void run() { try { try { initStreamAudioToServer(); Log.d(TAG, "WebSocket Connection established"); } catch (IOException e1) { Log.e(TAG, "IOException: " + e1.getMessage()); throw e1; } catch (InterruptedException e1) { Log.e(TAG, "InterruptedException:" + e1.getMessage()); throw e1; } catch (Exception e1) { Log.e(TAG, "Exception: " + e1.getMessage()); throw e1; } } catch (Exception e) { e.printStackTrace(); Log.e(TAG, "Connection failed: " + (e == null ? "null exception" : e.getMessage())); uploadPrepared = false; close(); } }; }; initStreamToServerThread.setName("initStreamToServerThread"); initStreamToServerThread.start(); } /** * Write string into socket * * @param message */ public void upload(String message){ try{ this.send(message); } catch(NotYetConnectedException ex){ Log.e(TAG, ex.getLocalizedMessage()); } } /** * Write data into socket * * @param data */ public void upload(byte[] data){ try{ this.send(data); } catch(NotYetConnectedException ex){ Log.e(TAG, ex.getLocalizedMessage()); } } /** * Stop by sending out zero byte of data */ public void stop(){ byte[] stopData = new byte[0]; this.upload(stopData); } @Override public void close() { Log.d(TAG, "closing the websocket"); super.close(); } @Override public void onClose(int code, String reason, boolean remote) { Log.d(TAG, "WebSocket closed"); this.uploadPrepared = false; Log.d(TAG, "### Code: " + code + " reason: " + reason + " remote: " + remote); if (delegate != null){ delegate.onClose(code, reason, remote); } } @Override public void onError(Exception ex) { Log.e(TAG, "WebSocket error"); String errorMessage = ""; if(ex != null) errorMessage = ex.getMessage(); // Send the error message to the delegate this.uploadPrepared = false; //this.sendMessage(ISpeechDelegate.ERROR); if (delegate != null){ delegate.onError(errorMessage); } } @Override public void onMessage(String message) { Log.d(TAG + "onMessage", message); if (delegate != null){ delegate.onMessage(message); } } @Override public void onOpen(ServerHandshake arg0) { Log.d(TAG, "WS connection opened successfully"); this.uploadPrepared = true; if (delegate != null){ delegate.onOpen(); } } private void sendSpeechHeader() { JSONObject obj = new JSONObject(); try { obj.put("action", "start"); obj.put("content-type", this.sConfig.audioFormat); obj.put("interim_results", this.sConfig.returnInterimResults); obj.put("continuous", this.sConfig.continuous); obj.put("inactivity_timeout", this.sConfig.inactivityTimeout); if (this.sConfig.maxAlternatives > 1) { obj.put("max_alternatives", this.sConfig.maxAlternatives); } if (!(Float.isNaN(this.sConfig.wordAlternativesThreshold))) { obj.put("word_alternatives_threshold", this.sConfig.wordAlternativesThreshold); } } catch (JSONException e) { e.printStackTrace(); } String startHeader = obj.toString(); this.upload(startHeader); this.encoder.onStart(); Log.d(TAG, "Sending init message: " + startHeader); } /** * Set delegate * * @param delegate */ public void setDelegate(ISpeechDelegate delegate) { this.delegate = delegate; } }